﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;

using ProjectBase.Core;

namespace $safeprojectname$.Publishing.Handlers.ExceptionHandler
{
    public class ExceptionStringBuilder
    {
        #region members
        protected const string TextDecoration = "*********************************************\n"; // 45 '*'
        #endregion members

        public virtual string Build(Exception exception)
        {
            return Build(exception, null);
        }

        public virtual string Build(Exception exception, NameValueCollection additionalInfo)
        {
            Contract.Requires(exception.Is(), "ExceptionToString: exception parameter cannot be null");

            // Create StringBuilder to maintain publishing information.
            var builder = new StringBuilder();

            builder.AppendFormat("A {1} has been thrown: {2}{0}", Environment.NewLine, exception.GetType().Name, exception.Message);

            // Record the contents of the AdditionalInfo collection.
            if (additionalInfo.Is())
            {
                builder.AppendFormat("{0}General Information:{0}{1}", Environment.NewLine, TextDecoration);
                AppendDictionary(builder, additionalInfo);
            }

            // Record the contents of the exception
            Exception ex = exception;
            int exceptionDeepth = 1; // Count variable to track the number of exceptions in the chain.
            while (ex.Is())
            {
                AppendExceptionInformation(builder, ex, exceptionDeepth);
                ex = ex.InnerException;
                ++exceptionDeepth;
            }

            return builder.ToString();
        }

        #region protected exception builders
        protected virtual void AppendExceptionInformation(StringBuilder builder, Exception exception, int exceptionDepth)
        {
            Contract.Requires(builder.Is());
            Contract.Requires(exception.Is());

            // Write title information for the exception object.
            builder.AppendFormat("{0}{0}{1}. Exception Information{0}{2}", Environment.NewLine, exceptionDepth, TextDecoration);
            builder.AppendFormat("{0}Exception Type: {1}", Environment.NewLine, exception.GetType().FullName);

            // exception was not thrown
            if (exception.TargetSite == null)
            {
                builder.AppendFormat("{0}Assembly: {1}", Environment.NewLine, exception.GetType().Module);
                builder.AppendFormat("{0}Class: {1}", Environment.NewLine, "");
                builder.AppendFormat("{0}Method: {1}", Environment.NewLine, "");
            }
            // exception was thrown
            else
            {
                var moduleName = exception.GetType().Module.Name;
                var className = string.Empty;

                var declaringType = exception.TargetSite.DeclaringType;
                if (declaringType.Is())
                {
                    moduleName = declaringType.Module.Is() ? declaringType.Module.Name : moduleName;
                    className = declaringType.Name;
                }
                builder.AppendFormat("{0}Assembly: {1}", Environment.NewLine, moduleName);
                builder.AppendFormat("{0}Class: {1}", Environment.NewLine, className);
                builder.AppendFormat("{0}Method: {1}", Environment.NewLine, exception.TargetSite);
            }

            // Loop through the public properties of the exception object and record their value.
            PropertyInfo[] api = exception.GetType().GetProperties();
            object args;
            foreach (var pi in api)
            {
                // Do not log information for the InnerException or StackTrace. This information is
                // captured later in the process.
                if ((pi.Name == "InnerException") || (pi.Name == "StackTrace") || (pi.Name == "Data"))
                    continue;

                try
                {
                    args = pi.GetValue(exception, null);
                }
                catch (Exception)
                {
                    // in case of a problem, just ignore the property
                    continue;
                }

                if (args.IsNull())
                {
                    builder.AppendFormat("{0}{1}: NULL", Environment.NewLine, pi.Name);
                }
                else
                {
                    builder.AppendFormat("{0}{1}: {2}", Environment.NewLine, pi.Name, args);
                }
            }

            // Record the data dictionary
            AppendDictionary(builder, exception.Data);

            // Record the StackTrace with separate label.
            if (exception.StackTrace.IsNull())
            {
                return;
            }
            builder.AppendFormat("{0}{0}{1}StackTrace Information:{0}", Environment.NewLine, TextDecoration);
            builder.AppendFormat("{0}{1}", Environment.NewLine, exception.StackTrace);

        }

        protected virtual void AppendDictionary(StringBuilder builder, IDictionary dictionary)
        {
            Contract.Requires(builder.Is());

            if (dictionary.IsNull() || dictionary.Count == 0)
            {
                return;
            }

            // sort the keys
            var keyObjects = new object[dictionary.Count];
            var keys = new string[dictionary.Count];
            dictionary.Keys.CopyTo(keyObjects, 0);

            for (int i = 0; i < keyObjects.Length; ++i)
            {
                keys[i] = keyObjects[i].ToString().Replace(" ", "_");
            }
            Array.Sort(keys, keyObjects);

            // append the list
            builder.AppendFormat("{0}Data: ", Environment.NewLine);

            for (int i = 0; i < keyObjects.Length; ++i)
            {
                var key = keys[i];
                var keyObject = keyObjects[i];
                var value = string.Empty;
                if (dictionary.Contains(keyObject))
                {
                    value = dictionary[keyObject].ToString();
                }
                builder.AppendFormat("{0}  {1}: {2}", Environment.NewLine, key, value);
            }
        }

        protected virtual void AppendDictionary(StringBuilder builder, NameValueCollection collection)
        {
            Contract.Requires(builder.Is());
            Contract.Requires(collection.Is());

            // sort keys
            var keys = new string[collection.Count];
            collection.AllKeys.CopyTo(keys, 0);
            Array.Sort(keys);

            // append values
            foreach (var key in keys)
            {
                var value = string.Empty;
                if (collection.AllKeys.Contains(key))
                {
                    value = collection[key];
                }
                builder.AppendFormat("{0}{1}: {2}", Environment.NewLine, key, value);
            }
        }
        #endregion protected exception builders
    }
}
